package cn.edu.ptu.jobfairs.core.config;

import cn.edu.ptu.jobfairs.basic.common.DataResult;
import cn.edu.ptu.jobfairs.basic.common.Result;
import cn.edu.ptu.jobfairs.basic.common.User;
import cn.edu.ptu.jobfairs.basic.dto.response.UserDTO;
import cn.edu.ptu.jobfairs.core.security.MD5PasswordEncoder;
import cn.edu.ptu.jobfairs.core.utils.UserUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

import static cn.edu.ptu.jobfairs.basic.common.Errors.*;


@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private UserDetailsService userDetailsServiceImpl;

    @Resource
    private MD5PasswordEncoder md5PasswordEncoder;

    @Resource
    private DaoAuthenticationProvider authenticationProvider;


    @Override
    protected void configure(AuthenticationManagerBuilder auth){
        auth.authenticationProvider(authenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/index", "/assets/**", "/img/**", "/js/**", "/favicon.ico").permitAll()
                .antMatchers("/**").authenticated()
                    .and()
                .formLogin()
                    .loginPage("/login").loginProcessingUrl("/auth")
                    .usernameParameter("u").passwordParameter("t")
                    .successHandler(new SuccessHandler())
                    .failureHandler(new FailureHandler())
                    .permitAll()
                    .and()
                .logout()
                    .logoutUrl("/logout")
                    .logoutSuccessUrl("/").permitAll()
                    .and()
                .csrf().disable()
                .exceptionHandling().accessDeniedHandler(new AuthenticationAccessDeniedHandler()).and()
                .sessionManagement().maximumSessions(1).sessionRegistry(new SessionRegistryImpl());
    }

    private class FailureHandler implements AuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
            if (e instanceof UsernameNotFoundException) writeResponse(httpServletResponse, Result.fail(CLIENT_ERROR, e.getMessage()));
            else if (e instanceof BadCredentialsException) writeResponse(httpServletResponse, Result.fail(CLIENT_ERROR, "密码错误"));
            else if (e instanceof DisabledException) writeResponse(httpServletResponse, Result.fail(CLIENT_ERROR, "用户已被禁用"));
            else {
                log.error("登录发生错误", e);
                writeResponse(httpServletResponse, Result.fail(SERVER_ERROR, "服务器发生错误"));
            }
        }
    }

    private class SuccessHandler implements AuthenticationSuccessHandler {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
            User user = UserUtils.getUser();
            if (user != null) {
                writeResponse(httpServletResponse, DataResult.ok(new UserDTO(user)));
            }
            else writeResponse(httpServletResponse, Result.fail(SERVER_ERROR, "登录失败"));
        }
    }

    private class AuthenticationAccessDeniedHandler implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {
            writeResponse(httpServletResponse, Result.fail(UNAUTHORIZED));
        }
    }

    private void writeResponse(HttpServletResponse httpServletResponse, Result result) throws IOException{
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.setCharacterEncoding("utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        out.write(JSON.toJSONString(result));
        out.flush();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setHideUserNotFoundExceptions(false);
        provider.setUserDetailsService(userDetailsServiceImpl);
        provider.setPasswordEncoder(md5PasswordEncoder);
        return provider;
    }
}
